/*
 * Dongle BUS interface Abstraction layer
 *   target serial buses like USB, SDIO, SPI, etc.
 *
 * Copyright (C) 1999-2019, Broadcom.
 *
 *      Unless you and Broadcom execute a separate written software license
 * agreement governing use of this software, this software is licensed to you
 * under the terms of the GNU General Public License version 2 (the "GPL"),
 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
 * following added to such license:
 *
 *      As a special exception, the copyright holders of this software give you
 * permission to link this software with independent modules, and to copy and
 * distribute the resulting executable under terms of your choice, provided that
 * you also meet, for each linked independent module, the terms and conditions
 * of the license of that module.  An independent module is a module which is
 * not derived from this software.  The special exception does not apply to any
 * modifications of the software.
 *
 *      Notwithstanding the above, under no circumstances may you combine this
 * software in any way with any other Broadcom software provided under a license
 * other than the GPL, without Broadcom's express prior written consent.
 *
 *
 * <<Broadcom-WL-IPTag/Open:>>
 *
 * $Id: dbus.h 686618 2017-02-23 07:20:43Z $
 */

#ifndef __DBUS_H__
#define __DBUS_H__

#include "typedefs.h"
#include <dhd_linux.h>

extern uint dbus_msglevel;
#define DBUS_ERROR_VAL 0x0001
#define DBUS_TRACE_VAL 0x0002
#define DBUS_INFO_VAL 0x0004

#if defined(DHD_DEBUG)
#define DBUSERR(args)                                                          \
    do {                                                                       \
        if (dbus_msglevel & DBUS_ERROR_VAL)                                    \
            printf args;                                                       \
    } while (0)
#define DBUSTRACE(args)                                                        \
    do {                                                                       \
        if (dbus_msglevel & DBUS_TRACE_VAL)                                    \
            printf args;                                                       \
    } while (0)
#define DBUSINFO(args)                                                         \
    do {                                                                       \
        if (dbus_msglevel & DBUS_INFO_VAL)                                     \
            printf args;                                                       \
    } while (0)
#else /* defined(DHD_DEBUG) */
#define DBUSERR(args)
#define DBUSTRACE(args)
#define DBUSINFO(args)
#endif

enum {
    DBUS_OK = 0,
    DBUS_ERR = -200,
    DBUS_ERR_TIMEOUT,
    DBUS_ERR_DISCONNECT,
    DBUS_ERR_NODEVICE,
    DBUS_ERR_UNSUPPORTED,
    DBUS_ERR_PENDING,
    DBUS_ERR_NOMEM,
    DBUS_ERR_TXFAIL,
    DBUS_ERR_TXTIMEOUT,
    DBUS_ERR_TXDROP,
    DBUS_ERR_RXFAIL,
    DBUS_ERR_RXDROP,
    DBUS_ERR_TXCTLFAIL,
    DBUS_ERR_RXCTLFAIL,
    DBUS_ERR_REG_PARAM,
    DBUS_STATUS_CANCELLED,
    DBUS_ERR_NVRAM,
    DBUS_JUMBO_NOMATCH,
    DBUS_JUMBO_BAD_FORMAT,
    DBUS_NVRAM_NONTXT,
    DBUS_ERR_RXZLP
};

#define BCM_OTP_SIZE_43236 84         /* number of 16 bit values */
#define BCM_OTP_SW_RGN_43236 24       /* start offset of SW config region */
#define BCM_OTP_ADDR_43236 0x18000800 /* address of otp base */

#define ERR_CBMASK_TXFAIL 0x00000001
#define ERR_CBMASK_RXFAIL 0x00000002
#define ERR_CBMASK_ALL 0xFFFFFFFF

#define DBUS_CBCTL_WRITE 0
#define DBUS_CBCTL_READ 1
#if defined(INTR_EP_ENABLE)
#define DBUS_CBINTR_POLL 2
#endif /* defined(INTR_EP_ENABLE) */

#define DBUS_TX_RETRY_LIMIT 3        /* retries for failed txirb */
#define DBUS_TX_TIMEOUT_INTERVAL 250 /* timeout for txirb complete, in ms */

#define DBUS_BUFFER_SIZE_TX 32000
#define DBUS_BUFFER_SIZE_RX 24000

#define DBUS_BUFFER_SIZE_TX_NOAGG 2048
#define DBUS_BUFFER_SIZE_RX_NOAGG 2048

/** DBUS types */
enum { DBUS_USB, DBUS_SDIO, DBUS_SPI, DBUS_UNKNOWN };

enum dbus_state {
    DBUS_STATE_DL_PENDING,
    DBUS_STATE_DL_DONE,
    DBUS_STATE_UP,
    DBUS_STATE_DOWN,
    DBUS_STATE_PNP_FWDL,
    DBUS_STATE_DISCONNECT,
    DBUS_STATE_SLEEP,
    DBUS_STATE_DL_NEEDED
};

enum dbus_pnp_state { DBUS_PNP_DISCONNECT, DBUS_PNP_SLEEP, DBUS_PNP_RESUME };

enum dbus_file { DBUS_FIRMWARE, DBUS_NVFILE };

typedef enum _DEVICE_SPEED {
    INVALID_SPEED = -1,
    LOW_SPEED = 1, /**< USB 1.1: 1.5 Mbps */
    FULL_SPEED,    /**< USB 1.1: 12  Mbps */
    HIGH_SPEED,    /**< USB 2.0: 480 Mbps */
    SUPER_SPEED,   /**< USB 3.0: 4.8 Gbps */
} DEVICE_SPEED;

typedef struct {
    int bustype;
    int vid;
    int pid;
    int devid;
    int chiprev; /**< chip revsion number */
    int mtu;
    int nchan; /**< Data Channels */
    int has_2nd_bulk_in_ep;
} dbus_attrib_t;

/* FIX: Account for errors related to DBUS;
 * Let upper layer account for packets/bytes
 */
typedef struct {
    uint32 rx_errors;
    uint32 tx_errors;
    uint32 rx_dropped;
    uint32 tx_dropped;
} dbus_stats_t;

/**
 * Configurable BUS parameters
 */
enum {
    DBUS_CONFIG_ID_RXCTL_DEFERRES = 1,
    DBUS_CONFIG_ID_AGGR_LIMIT,
    DBUS_CONFIG_ID_KEEPIF_ON_DEVRESET
};

typedef struct {
    uint32 config_id;
    union {
        uint32 general_param;
        bool rxctl_deferrespok;
        struct {
            int maxrxsf;
            int maxrxsize;
            int maxtxsf;
            int maxtxsize;
        } aggr_param;
    };
} dbus_config_t;

/**
 * External Download Info
 */
typedef struct dbus_extdl {
    uint8 *fw;
    int fwlen;
    uint8 *vars;
    int varslen;
} dbus_extdl_t;

struct dbus_callbacks;
struct exec_parms;

typedef void *(*probe_cb_t)(void *arg, const char *desc, uint32 bustype,
                            uint16 bus_no, uint16 slot, uint32 hdrlen);
typedef void (*disconnect_cb_t)(void *arg);
typedef void *(*exec_cb_t)(struct exec_parms *args);

/** Client callbacks registered during dbus_attach() */
typedef struct dbus_callbacks {
    void (*send_complete)(void *cbarg, void *info, int status);
    void (*recv_buf)(void *cbarg, uint8 *buf, int len);
    void (*recv_pkt)(void *cbarg, void *pkt);
    void (*txflowcontrol)(void *cbarg, bool onoff);
    void (*errhandler)(void *cbarg, int err);
    void (*ctl_complete)(void *cbarg, int type, int status);
    void (*state_change)(void *cbarg, int state);
    void *(*pktget)(void *cbarg, uint len, bool send);
    void (*pktfree)(void *cbarg, void *p, bool send);
} dbus_callbacks_t;

struct dbus_pub;
struct bcmstrbuf;
struct dbus_irb;
struct dbus_irb_rx;
struct dbus_irb_tx;
struct dbus_intf_callbacks;

typedef struct {
    void *(*attach)(struct dbus_pub *pub, void *cbarg,
                    struct dbus_intf_callbacks *cbs);
    void (*detach)(struct dbus_pub *pub, void *bus);

    int (*up)(void *bus);
    int (*down)(void *bus);
    int (*send_irb)(void *bus, struct dbus_irb_tx *txirb);
    int (*recv_irb)(void *bus, struct dbus_irb_rx *rxirb);
    int (*cancel_irb)(void *bus, struct dbus_irb_tx *txirb);
    int (*send_ctl)(void *bus, uint8 *buf, int len);
    int (*recv_ctl)(void *bus, uint8 *buf, int len);
    int (*get_stats)(void *bus, dbus_stats_t *stats);
    int (*get_attrib)(void *bus, dbus_attrib_t *attrib);

    int (*pnp)(void *bus, int evnt);
    int (*remove)(void *bus);
    int (*resume)(void *bus);
    int (*suspend)(void *bus);
    int (*stop)(void *bus);
    int (*reset)(void *bus);

    /* Access to bus buffers directly */
    void *(*pktget)(void *bus, int len);
    void (*pktfree)(void *bus, void *pkt);

    int (*iovar_op)(void *bus, const char *name, void *params, int plen,
                    void *arg, int len, bool set);
    void (*dump)(void *bus, struct bcmstrbuf *strbuf);
    int (*set_config)(void *bus, dbus_config_t *config);
    int (*get_config)(void *bus, dbus_config_t *config);

    bool (*device_exists)(void *bus);
    int (*dlneeded)(void *bus);
    int (*dlstart)(void *bus, uint8 *fw, int len);
    int (*dlrun)(void *bus);
    bool (*recv_needed)(void *bus);

    void *(*exec_rxlock)(void *bus, exec_cb_t func, struct exec_parms *args);
    void *(*exec_txlock)(void *bus, exec_cb_t func, struct exec_parms *args);

    int (*tx_timer_init)(void *bus);
    int (*tx_timer_start)(void *bus, uint timeout);
    int (*tx_timer_stop)(void *bus);

    int (*sched_dpc)(void *bus);
    int (*lock)(void *bus);
    int (*unlock)(void *bus);
    int (*sched_probe_cb)(void *bus);

    int (*shutdown)(void *bus);

    int (*recv_stop)(void *bus);
    int (*recv_resume)(void *bus);

    int (*recv_irb_from_ep)(void *bus, struct dbus_irb_rx *rxirb, uint ep_idx);

    int (*readreg)(void *bus, uint32 regaddr, int datalen, uint32 *value);

    /* Add from the bottom */
} dbus_intf_t;

typedef struct dbus_pub {
    struct osl_info *osh;
    dbus_stats_t stats;
    dbus_attrib_t attrib;
    enum dbus_state busstate;
    DEVICE_SPEED device_speed;
    int ntxq, nrxq, rxsize;
    void *bus;
    struct shared_info *sh;
    void *dev_info;
} dbus_pub_t;

#define BUS_INFO(bus, type) (((type *)bus)->pub->bus)

#define ALIGNED_LOCAL_VARIABLE(var, align)                                     \
    uint8 buffer[SDALIGN + 64];                                                \
    uint8 *var = (uint8 *)(((uintptr)&buffer[0]) & ~(align - 1)) + align;

/*
 * Public Bus Function Interface
 */

/*
 * FIX: Is there better way to pass OS/Host handles to DBUS but still
 *      maintain common interface for all OS??
 * Under NDIS, param1 needs to be MiniportHandle
 *  For NDIS60, param2 is WdfDevice
 * Under Linux, param1 and param2 are NULL;
 */
extern int dbus_register(int vid, int pid, probe_cb_t prcb,
                         disconnect_cb_t discb, void *prarg, void *param1,
                         void *param2);
extern int dbus_deregister(void);
extern int dbus_down(dbus_pub_t *pub);
extern int dbus_shutdown(dbus_pub_t *pub);
extern void dbus_flowctrl_rx(dbus_pub_t *pub, bool on);

extern int dbus_send_txdata(dbus_pub_t *dbus, void *pktbuf);
extern int dbus_send_buf(dbus_pub_t *pub, uint8 *buf, int len, void *info);
extern int dbus_send_pkt(dbus_pub_t *pub, void *pkt, void *info);
extern int dbus_recv_bulk(dbus_pub_t *pub, uint32 ep_idx);
extern int dbus_poll_intr(dbus_pub_t *pub);
extern int dbus_get_stats(dbus_pub_t *pub, dbus_stats_t *stats);
extern int dbus_get_device_speed(dbus_pub_t *pub);
extern int dbus_set_config(dbus_pub_t *pub, dbus_config_t *config);
extern int dbus_get_config(dbus_pub_t *pub, dbus_config_t *config);
extern void *dbus_get_devinfo(dbus_pub_t *pub);

extern void *dbus_pktget(dbus_pub_t *pub, int len);
extern void dbus_pktfree(dbus_pub_t *pub, void *pkt);

extern int dbus_set_errmask(dbus_pub_t *pub, uint32 mask);
extern int dbus_pnp_sleep(dbus_pub_t *pub);
extern int dbus_pnp_resume(dbus_pub_t *pub, int *fw_reload);
extern int dbus_pnp_disconnect(dbus_pub_t *pub);

extern void *dhd_dbus_txq(const dbus_pub_t *pub);
extern uint dhd_dbus_hdrlen(const dbus_pub_t *pub);

/*
 * Private Common Bus Interface
 */

/** IO Request Block (IRB) */
typedef struct dbus_irb {
    struct dbus_irb
        *next; /**< it's casted from dbus_irb_tx or dbus_irb_rx struct */
} dbus_irb_t;

typedef struct dbus_irb_rx {
    struct dbus_irb irb; /* Must be first */
    uint8 *buf;
    int buf_len;
    int actual_len;
    void *pkt;
    void *info;
    void *arg;
} dbus_irb_rx_t;

typedef struct dbus_irb_tx {
    struct dbus_irb irb; /** Must be first */
    uint8 *buf;          /** mutually exclusive with struct member 'pkt' */
    int len;             /** length of field 'buf' */
    void *pkt;           /** mutually exclusive with struct member 'buf' */
    int retry_count;
    void *info;
    void *arg;
    void
        *send_buf; /**< linear  bufffer for LINUX when aggreagtion is enabled */
} dbus_irb_tx_t;

/**
 * DBUS interface callbacks are different from user callbacks
 * so, internally, different info can be passed to upper layer
 */
typedef struct dbus_intf_callbacks {
    void (*send_irb_timeout)(void *cbarg, dbus_irb_tx_t *txirb);
    void (*send_irb_complete)(void *cbarg, dbus_irb_tx_t *txirb, int status);
    void (*recv_irb_complete)(void *cbarg, dbus_irb_rx_t *rxirb, int status);
    void (*errhandler)(void *cbarg, int err);
    void (*ctl_complete)(void *cbarg, int type, int status);
    void (*state_change)(void *cbarg, int state);
    bool (*isr)(void *cbarg, bool *wantdpc);
    bool (*dpc)(void *cbarg, bool bounded);
    void (*watchdog)(void *cbarg);
    void *(*pktget)(void *cbarg, uint len, bool send);
    void (*pktfree)(void *cbarg, void *p, bool send);
    struct dbus_irb *(*getirb)(void *cbarg, bool send);
    void (*rxerr_indicate)(void *cbarg, bool on);
} dbus_intf_callbacks_t;

/*
 * Porting: To support new bus, port these functions below
 */

/*
 * Bus specific Interface
 * Implemented by dbus_usb.c/dbus_sdio.c
 */
extern int dbus_bus_register(int vid, int pid, probe_cb_t prcb,
                             disconnect_cb_t discb, void *prarg,
                             dbus_intf_t **intf, void *param1, void *param2);
extern int dbus_bus_deregister(void);
extern void dbus_bus_fw_get(void *bus, uint8 **fw, int *fwlen, int *decomp);

/*
 * Bus-specific and OS-specific Interface
 * Implemented by dbus_usb_[linux/ndis].c/dbus_sdio_[linux/ndis].c
 */
extern int dbus_bus_osl_register(int vid, int pid, probe_cb_t prcb,
                                 disconnect_cb_t discb, void *prarg,
                                 dbus_intf_t **intf, void *param1,
                                 void *param2);
extern int dbus_bus_osl_deregister(void);

/*
 * Bus-specific, OS-specific, HW-specific Interface
 * Mainly for SDIO Host HW controller
 */
extern int dbus_bus_osl_hw_register(int vid, int pid, probe_cb_t prcb,
                                    disconnect_cb_t discb, void *prarg,
                                    dbus_intf_t **intf);
extern int dbus_bus_osl_hw_deregister(void);

extern uint usbdev_bulkin_eps(void);
#if defined(BCM_REQUEST_FW)
extern void *dbus_get_fw_nvfile(int devid, int chiprev, uint8 **fw, int *fwlen,
                                int type, uint16 boardtype, uint16 boardrev);
extern void dbus_release_fw_nvfile(void *firmware);
#endif /* #if defined(BCM_REQUEST_FW) */

#if defined(EHCI_FASTPATH_TX) || defined(EHCI_FASTPATH_RX)

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0))
/* Backward compatibility */
typedef unsigned int gfp_t;

#define dma_pool pci_pool
#define dma_pool_create(name, dev, size, align, alloc)                         \
    pci_pool_create(name, dev, size, align, alloc, GFP_DMA | GFP_ATOMIC)
#define dma_pool_destroy(pool) pci_pool_destroy(pool)
#define dma_pool_alloc(pool, flags, handle) pci_pool_alloc(pool, flags, handle)
#define dma_pool_free(pool, vaddr, addr) pci_pool_free(pool, vaddr, addr)

#define dma_map_single(dev, addr, size, dir)                                   \
    pci_map_single(dev, addr, size, dir)
#define dma_unmap_single(dev, hnd, size, dir)                                  \
    pci_unmap_single(dev, hnd, size, dir)
#define DMA_FROM_DEVICE PCI_DMA_FROMDEVICE
#define DMA_TO_DEVICE PCI_DMA_TODEVICE
#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) */

/* Availability of these functions varies (when present, they have two
 * arguments) */
#ifndef hc32_to_cpu
#define hc32_to_cpu(x) le32_to_cpu(x)
#define cpu_to_hc32(x) cpu_to_le32(x)
typedef unsigned int __hc32;
#else
#error Two-argument functions needed
#endif // endif

/* Private USB opcode base */
#define EHCI_FASTPATH 0x31
#define EHCI_SET_EP_BYPASS EHCI_FASTPATH
#define EHCI_SET_BYPASS_CB (EHCI_FASTPATH + 1)
#define EHCI_SET_BYPASS_DEV (EHCI_FASTPATH + 2)
#define EHCI_DUMP_STATE (EHCI_FASTPATH + 3)
#define EHCI_SET_BYPASS_POOL (EHCI_FASTPATH + 4)
#define EHCI_CLR_EP_BYPASS (EHCI_FASTPATH + 5)

/*
 * EHCI QTD structure (hardware and extension)
 * NOTE that is does not need to (and does not) match its kernel counterpart
 */
#define EHCI_QTD_NBUFFERS 5
#define EHCI_QTD_ALIGN 32
#define EHCI_BULK_PACKET_SIZE 512
#define EHCI_QTD_XACTERR_MAX 32

struct ehci_qtd {
    /* Hardware map */
    volatile uint32_t qtd_next;
    volatile uint32_t qtd_altnext;
    volatile uint32_t qtd_status;
#define EHCI_QTD_GET_BYTES(x) (((x) >> 16) & 0x7fff)
#define EHCI_QTD_IOC 0x00008000
#define EHCI_QTD_GET_CERR(x) (((x) >> 10) & 0x3)
#define EHCI_QTD_SET_CERR(x) ((x) << 10)
#define EHCI_QTD_GET_PID(x) (((x) >> 8) & 0x3)
#define EHCI_QTD_SET_PID(x) ((x) << 8)
#define EHCI_QTD_ACTIVE 0x80
#define EHCI_QTD_HALTED 0x40
#define EHCI_QTD_BUFERR 0x20
#define EHCI_QTD_BABBLE 0x10
#define EHCI_QTD_XACTERR 0x08
#define EHCI_QTD_MISSEDMICRO 0x04
    volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS];
    volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS];

    /* Implementation extension */
    dma_addr_t qtd_self;       /**< own hardware address */
    struct ehci_qtd *obj_next; /**< software link to the next QTD */
    void *rpc;                 /**< pointer to the rpc buffer */
    size_t length;             /**< length of the data in the buffer */
    void *buff;                /**< pointer to the reassembly buffer */
    int xacterrs;              /**< retry counter for qtd xact error */
} __attribute__((aligned(EHCI_QTD_ALIGN)));

#define EHCI_NULL __constant_cpu_to_le32(1) /* HW null pointer shall be odd */

#define SHORT_READ_Q(token)                                                    \
    (EHCI_QTD_GET_BYTES(token) != 0 && EHCI_QTD_GET_PID(token) == 1)

/**
 * Queue Head
 * NOTE This structure is slightly different from the one in the kernel; but
 * needs to stay compatible.
 */
struct ehci_qh {
    /* Hardware map */
    volatile uint32_t qh_link;
    volatile uint32_t qh_endp;
    volatile uint32_t qh_endphub;
    volatile uint32_t qh_curqtd;

    /* QTD overlay */
    volatile uint32_t ow_next;
    volatile uint32_t ow_altnext;
    volatile uint32_t ow_status;
    volatile uint32_t ow_buffer[EHCI_QTD_NBUFFERS];
    volatile uint32_t ow_buffer_hi[EHCI_QTD_NBUFFERS];

    /* Extension (should match the kernel layout) */
    dma_addr_t unused0;
    void *unused1;
    struct list_head unused2;
    struct ehci_qtd *dummy;
    struct ehci_qh *unused3;

    struct ehci_hcd *unused4;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
    struct kref unused5;
    unsigned unused6;

    uint8_t unused7;

    /* periodic schedule info */
    uint8_t unused8;
    uint8_t unused9;
    uint8_t unused10;
    uint16_t unused11;
    uint16_t unused12;
    uint16_t unused13;
    struct usb_device *unused14;
#else
    unsigned unused5;

    u8 unused6;

    /* periodic schedule info */
    u8 unused7;
    u8 unused8;
    u8 unused9;
    unsigned short unused10;
    unsigned short unused11;
#define NO_FRAME ((unsigned short)~0)
#ifdef EHCI_QUIRK_FIX
    struct usb_device *unused12;
#endif /* EHCI_QUIRK_FIX */
#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) */
    struct ehci_qtd *first_qtd;
    /* Link to the first QTD; this is an optimized equivalent of the qtd_list
     * field */
    /* NOTE that ehci_qh in ehci.h shall reserve this word */
} __attribute__((aligned(EHCI_QTD_ALIGN)));

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0))
/** The corresponding structure in the kernel is used to get the QH */
struct hcd_dev { /* usb_device.hcpriv points to this */
    struct list_head unused0;
    struct list_head unused1;

    /* array of QH pointers */
    void *ep[32];
};
#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) */

int optimize_qtd_fill_with_rpc(const dbus_pub_t *pub, int epn,
                               struct ehci_qtd *qtd, void *rpc, int token,
                               int len);
int optimize_qtd_fill_with_data(const dbus_pub_t *pub, int epn,
                                struct ehci_qtd *qtd, void *data, int token,
                                int len);
int optimize_submit_async(struct ehci_qtd *qtd, int epn);
void inline optimize_ehci_qtd_init(struct ehci_qtd *qtd, dma_addr_t dma);
struct ehci_qtd *optimize_ehci_qtd_alloc(gfp_t flags);
void optimize_ehci_qtd_free(struct ehci_qtd *qtd);
void optimize_submit_rx_request(const dbus_pub_t *pub, int epn,
                                struct ehci_qtd *qtd_in, void *buf);
#endif /* EHCI_FASTPATH_TX || EHCI_FASTPATH_RX */

void dbus_flowctrl_tx(void *dbi, bool on);
#endif /* __DBUS_H__ */
